﻿@page "/admin/users"
@using BlazorBoilerplate.Shared.Dto.Account
@attribute [Authorize]
@inject HttpClient Http
@inject IAuthorizationService AuthorizationService
@inject AuthenticationStateProvider authStateProvider
@inject IMatToaster matToaster
@inject AppState appState
@using System.Net

<h1>Users</h1>
<p>User Management.</p>

@if (users == null)
{
    <LoadingBackground ShowLogoBox="true">
        <label>Loading Users</label>
    </LoadingBackground>
}
else
{
    <MatTable Class="mat-elevation-z5" Items="@users" LoadInitialData="true" Striped="true" RequestApiOnlyOnce="true" ApiUrl="api/users"
              DebounceMilliseconds="150">
        <MatTableHeader>
            <th><MatButton Icon="person_add" Label="New User" OnClick="@(e => { createUserDialogOpen = true; })"></MatButton></th>
            <th>User Name</th>
            <th>Email</th>
            <th>Roles</th>
        </MatTableHeader>
        <MatTableRow Context="UserRow">
            <td>
                <div style="width:155px;">
                    <AuthorizeView Policy="@Policies.IsAdmin" Context="AuthorizeContext">
                        <Authorized>
                            <MatIconButton Icon="edit" OnClick="@(() => OpenEditDialog(UserRow.UserId))"></MatIconButton>
                        </Authorized>
                        <NotAuthorized>
                            <MatIconButton Icon="edit" Disabled="true"></MatIconButton>
                        </NotAuthorized>
                    </AuthorizeView>
                    <MatIconButton Icon="rotate_right" OnClick="@(() => OpenResetPasswordDialog(UserRow.UserName, UserRow.UserId))" Disabled="@(UserRow.UserId != userProfile.UserId)"></MatIconButton>
                    <AuthorizeView Policy="@Policies.IsAdmin" Context="AuthorizeContext">
                        <Authorized>
                            <MatIconButton Icon="delete" OnClick="@(() => OpenDeleteDialog(UserRow.UserId))"></MatIconButton>
                        </Authorized>
                        <NotAuthorized>
                            <MatIconButton Icon="delete" Disabled="true"></MatIconButton>
                        </NotAuthorized>
                    </AuthorizeView>
                </div>
            </td>

            <td><div style="width:130px;">@UserRow.UserName</div></td>
            <td>@UserRow.Email</td>
            <td>
                <MatChipSet>
                    @foreach (var role in @UserRow.Roles)
                        {
                        <MatChip Label="@role"></MatChip>
                        }
                </MatChipSet>
            </td>
        </MatTableRow>
    </MatTable>
}

<MatDialog @bind-IsOpen="@createUserDialogOpen">
    <MatDialogTitle>Create User</MatDialogTitle>
    <MatDialogContent>
        <EditForm Model="@registerParameters" OnValidSubmit="@CreateUserAsync">
            <DataAnnotationsValidator />
            <ValidationSummary />
            <fieldset>
                <div class="form-group">
                    <MatTextField @bind-Value="@registerParameters.UserName" Label="User Name" Icon="person" IconTrailing="true" FullWidth="true" Required="true"></MatTextField>
                </div>
                <div class="form-group">
                    <MatTextField @bind-Value="@registerParameters.Email" Label="Email" Icon="mail_outline" IconTrailing="true" FullWidth="true" Required="true"></MatTextField>
                </div>
                <div class="form-group">
                    <MatTextField @bind-Value="@registerParameters.Password" Label="Password" Icon="lock_outline" IconTrailing="true" FullWidth="true" Required="true" Type="password"></MatTextField>
                </div>
                <div class="form-group">
                    <MatTextField @bind-Value="@registerParameters.PasswordConfirm" Label="Password Confirmation" Icon="lock_outline" IconTrailing="true" FullWidth="true" Required="true" Type="password"></MatTextField>
                </div>
            </fieldset>
        </EditForm>
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@(e => { createUserDialogOpen = false; })">Cancel</MatButton>
        <MatButton OnClick="@CreateUserAsync">Create User</MatButton>
    </MatDialogActions>
</MatDialog>

<MatDialog @bind-IsOpen="@editDialogOpen">
    <MatDialogTitle>Edit User</MatDialogTitle>
    <MatDialogContent>
        <EditForm Model="@user" OnValidSubmit="@UpdateUserAsync">
            <DataAnnotationsValidator />
            <ValidationSummary />
            <fieldset>
                <div class="form-group">
                    <MatTextField @bind-Value="@user.UserName" Label="User Name" Icon="person" IconTrailing="true" FullWidth="true" Required="true"></MatTextField>
                </div>
                <div class="form-group">
                    <MatTextField @bind-Value="@user.Email" Label="Email" Icon="mail_outline" IconTrailing="true" FullWidth="true" Required="true" Type="mail"></MatTextField>
                </div>
                <MatChipSet>
                    @foreach (var role in roleSelections.OrderBy(x => x.Name))
                    {
                        <MatChip Label="@role.Name" LeadingIcon="@( (role.IsSelected) ? "done" : "")" @onclick="@(()=>UpdateUserRole(role))" />
                    }
                </MatChipSet>
            </fieldset>
        </EditForm>
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@CancelChanges">Cancel</MatButton>
        <MatButton OnClick="@UpdateUserAsync">Update User</MatButton>
    </MatDialogActions>
</MatDialog>

<MatDialog @bind-IsOpen="@deleteUserDialogOpen" Style="z-index:100">
    <MatDialogTitle><MatIcon Icon="warning"></MatIcon> Confirm Delete</MatDialogTitle>
    <MatDialogContent>
        Are you sure you want to delete user "@user.UserName" ?
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@(e => { deleteUserDialogOpen = false; })">Cancel</MatButton>
        <MatButton OnClick="@DeleteUserAsync">Delete</MatButton>
    </MatDialogActions>
</MatDialog>

<MatDialog @bind-IsOpen="@resetPasswordDialogOpen">
    <MatDialogTitle>Manual Password Reset for @registerParameters.UserName</MatDialogTitle>
    <MatDialogContent>
        <EditForm Model="@registerParameters" OnValidSubmit="@ResetUserPasswordAsync">
            <DataAnnotationsValidator />
            <ValidationSummary />
            <fieldset>
                <div class="form-group">
                    <MatTextField @bind-Value="@registerParameters.Password" Label="Password" Icon="lock_outline" IconTrailing="true" FullWidth="true" Required="true" Type="password"></MatTextField>
                </div>
                <div class="form-group">
                    <MatTextField @bind-Value="@registerParameters.PasswordConfirm" Label="Password Confirmation" Icon="lock_outline" IconTrailing="true" FullWidth="true" Required="true" Type="password"></MatTextField>
                </div>
            </fieldset>
        </EditForm>
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@(e => { resetPasswordDialogOpen = false; })">Cancel</MatButton>
        <MatButton OnClick="@ResetUserPasswordAsync">Reset User Password</MatButton>
    </MatDialogActions>
</MatDialog>

@code {
    //Auth documentation
    //https://docs.microsoft.com/en-us/aspnet/core/security/blazor

    int pageSize { get; set; } = 15;
    int currentPage { get; set; } = 0;

    bool createUserDialogOpen = false;
    bool editDialogOpen = false;
    bool deleteUserDialogOpen = false;
    bool resetPasswordDialogOpen = false;

    List<UserInfoDto> users;
    List<RoleSelection> roleSelections { get; set; } = new List<RoleSelection>();

    public UserInfoDto user { get; set; } = new UserInfoDto(); // Holds user being actively modified or created
    RegisterDto registerParameters { get; set; } = new RegisterDto();

    UserProfileDto userProfile = new UserProfileDto();

    public class RoleSelection
    {
        public bool IsSelected { get; set; }
        public string Name { get; set; }
    };

    protected override async Task OnInitializedAsync()
    {
        await RetrieveUserListAsync();
        await PopulateRoleList();
        userProfile = await appState.GetUserProfile();
    }

    public async Task RetrieveUserListAsync()
    {
        try
        {
            var apiResponse = await Http.GetFromJsonAsync<ApiResponseDto<List<UserInfoDto>>>($"api/Admin/Users?pageSize={pageSize}&pageNumber={currentPage}");

            if (apiResponse.IsSuccessStatusCode)
            {
                matToaster.Add(apiResponse.Message, MatToastType.Success, "Users Retrieved");
                users = apiResponse.Result;
            }
            else
                matToaster.Add(apiResponse.Message + " : " + apiResponse.StatusCode, MatToastType.Danger, "User Retrieval Failed");
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, "User Retrieval Error");
        }
    }

    public void OpenEditDialog(Guid userId)
    {
        user = new UserInfoDto(); // reset user object
        user = users.Where(x => x.UserId == userId).SingleOrDefault();  // load the user information into the temp user object for worry free editing
        foreach (var role in roleSelections)
            role.IsSelected = user.Roles.Contains(role.Name);

        user.SaveState();
        editDialogOpen = true;
    }

    public void OpenResetPasswordDialog(string userName, Guid userId)
    {
        // reusing the registerParameters and User objects
        registerParameters = new RegisterDto();  // reset object
        registerParameters.UserName = userName;
        user.UserId = userId;
        resetPasswordDialogOpen = true;
    }

    public void OpenDeleteDialog(Guid userId)
    {
        user = users.Where(x => x.UserId == userId).SingleOrDefault();
        deleteUserDialogOpen = true;
    }

    protected void UpdateUserRole(RoleSelection roleSelectionItem)
    {
        roleSelectionItem.IsSelected = !roleSelectionItem.IsSelected;
    }

    public async Task PopulateRoleList()
    {
        try
        {
            var roleNames = new List<string>();
            var response = await Http.GetFromJsonAsync<ApiResponseDto<List<string>>>("api/Account/ListRoles");

            roleNames = response.Result;

            roleSelections = new List<RoleSelection>();// clear out list

            // initialize selection list with all un-selected
            foreach (string role in roleNames)
            {
                roleSelections.Add(new RoleSelection
                {
                    Name = role,
                    IsSelected = false
                });
            }
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, "Role Retrieval Error");
        }
    }

    public void CancelChanges()
    {
        user.RestoreState();
        editDialogOpen = false;
    }

    public async Task UpdateUserAsync()
    {
        try
        {
            //update the user object's role list with the new selection set
            user.Roles = roleSelections.Where(x => x.IsSelected == true).Select(x => x.Name).ToList();

            var apiResponse = await Http.PutJsonAsync<ApiResponseDto>("api/Account", user);

            if (apiResponse.IsSuccessStatusCode)
                matToaster.Add("User Updated", MatToastType.Success);
            else
            {
                matToaster.Add("Error", MatToastType.Danger, apiResponse.StatusCode.ToString());
                user.RestoreState();
            }

            editDialogOpen = false;
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, "User Update Error");
        }
        finally
        {
            user.ClearState();
        }
    }

    public async Task CreateUserAsync()
    {
        try
        {
            if (registerParameters.Password != registerParameters.PasswordConfirm)
            {
                matToaster.Add("Password Confirmation Failed", MatToastType.Danger, "");
                return;
            }

            var apiResponse = await ((IdentityAuthenticationStateProvider)authStateProvider).Create(registerParameters);
            if (apiResponse.IsSuccessStatusCode)
            {
                matToaster.Add(apiResponse.Message, MatToastType.Success);
                user = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfoDto>(apiResponse.Result.ToString());
                users.Add(user);
                registerParameters = new RegisterDto(); //reset create user object after insert
                createUserDialogOpen = false;
            }
            else
            {
                matToaster.Add(apiResponse.Message + " : " + apiResponse.StatusCode, MatToastType.Danger, "User Creation Failed");
            }
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, "User Creation Error");
        }
    }

    public async Task ResetUserPasswordAsync()
    {
        try
        {
            if (registerParameters.Password != registerParameters.PasswordConfirm)
            {
                matToaster.Add("Passwords Must Match", MatToastType.Warning);
            }
            else
            {
                var apiResponse = await Http.PostJsonAsync<ApiResponseDto>($"api/Account/AdminUserPasswordReset/{user.UserId}", registerParameters.Password);

                if (apiResponse.IsSuccessStatusCode)
                    matToaster.Add("Password Reset", MatToastType.Success, apiResponse.Message);
                else
                    matToaster.Add(apiResponse.Message, MatToastType.Danger);

                resetPasswordDialogOpen = false;
            }
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, "Password Reset Error");
        }
    }

    public async Task DeleteUserAsync()
    {
        try
        {
            var response = await Http.DeleteAsync($"api/Account/{user.UserId}");

            if (response.StatusCode == (HttpStatusCode)Status200OK)
            {
                matToaster.Add("User Deleted", MatToastType.Success);
                users.Remove(user);
                deleteUserDialogOpen = false;
                StateHasChanged();
            }
            else
                matToaster.Add("User Delete Failed", MatToastType.Danger);
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, "User Delete Error");
        }
    }
}
